/* Validations.java

{{IS_NOTE
 Purpose:
  
 Description:
  
 History:
  May 13, 2011 12:07:28 PM , Created by simonpai
}}IS_NOTE

Copyright (C) 2011 Potix Corporation. All Rights Reserved.

{{IS_RIGHT
}}IS_RIGHT
*/
package demo.web.ui.ctrl;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;

import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.WrongValueException;
import org.zkoss.zk.ui.WrongValuesException;
import org.zkoss.zkplus.databind.Binding;
import org.zkoss.zkplus.databind.BindingValidateEvent;

/**
 * A utility class for JSR 303 Bean Validation
 * @author simonpai
 */
public class Validations {
	
	private static final Validator _validator = 
		Validation.buildDefaultValidatorFactory().getValidator();
	
	/**
	 * Validate values to be updated to the bean in data binding against the 
	 * JSR 303 constraints. The constraints must be specified on getter methods
	 * rather than fields.
	 * @param runner an optional runner called per binding
	 */
	public static void validate(BindingValidateEvent event, ViolationRunner runner) {
		
		@SuppressWarnings("unchecked")
		Iterator<Component> refs = event.getReferences().iterator();
		@SuppressWarnings("unchecked")
		Iterator<Binding> bindings = event.getBindings().iterator();
		Iterator<?> values = event.getValues().iterator();
		
		List<WrongValueException> wvelist = new ArrayList<WrongValueException>();
		
		while(refs.hasNext() && bindings.hasNext() && values.hasNext()) {
			
			Component ref = refs.next();
			Binding binding = bindings.next();
			Object value = values.next();
			
			Object bean = binding.getBean(ref);
			String expr = binding.getExpression();
			String propName = expr.substring(expr.lastIndexOf('.') + 1);
			
			@SuppressWarnings("unchecked")
			Set<ConstraintViolation<?>> vs = validate(bean.getClass(), propName, value);
			
			// collect WrongValueException
			for(ConstraintViolation<?> v : vs)
				wvelist.add(new WrongValueException(ref, v.getMessage()));
			
			// call runner
			if(runner != null)
				runner.run(vs, bean, ref);
		}
		
		// this will interrupt binding value to bean, so bean is not modified
		if(!wvelist.isEmpty())
			throw new WrongValuesException(wvelist.toArray(new WrongValueException[0]));
	}
	
	// interface //
	public interface ViolationRunner {
		
		/**
		 * @param ref The reference component that binds to the bean
		 */
		public void run(Set<ConstraintViolation<?>> violations, Object bean, Component ref);
	}
	
	// helper //
	@SuppressWarnings("unchecked")
	private static Set validate(Class clazz, String propName, Object value) {
		// for working around Generics limitation
		return _validator.validateValue(clazz, propName, value);
	}
	
}
